
#include <queue>
#include "core/timer.h"
#include "core/service.h"
#include "core/core_message.h"
#include "x_endian.h"

struct TimeNode {
  TimeNode(void) 
      : session(0), 
        user_data(NULL),
        trigger_count(0),
        timer_cb(0) {}

  TimeNode(const Handle& h, uint32_t s, void* ud, uint32_t tc, 
           uintptr_t cb, const utils::milliseconds& d) 
      : handle(h), 
        session(s),
        user_data(ud),
        trigger_count(tc), 
        timer_cb(cb),
        duration(d) {
    next_time = utils::steady_clock::now() + duration; 
  }

  Handle handle;
  uint32_t session;
  void* user_data;
  int32_t trigger_count;
  uintptr_t timer_cb;
  utils::milliseconds duration;
  utils::steady_clock::time_point next_time;
};

struct Timers::TimersImpl {
  // here what we want is a 'small heap' which will sort to place the node
  // with smaller timepoint value to the top, if the top timepoint is not
  // reached, the rest need not to be checked
  // note: i reverse the strictly less order 
  TimersImpl(void) 
      : timenode_queue([](const TimeNode* left, const TimeNode* right) { 
                         return left->next_time > right->next_time; }) 
  {}

  std::priority_queue<TimeNode*, std::vector<TimeNode*>,
      bool (*)(const TimeNode* left, const TimeNode* right)> timenode_queue;
  boost::mutex queue_mutex;
};

Timers::Timers(void) : pimpl_(new Timers::TimersImpl) {}

namespace {
void Trigger(const Handle& handle, uint32_t session, 
             void* user_data, uintptr_t timer_cb) {
  uintptr_t* data = new uintptr_t[2];
  data[0] = htobe_ptr(reinterpret_cast<uintptr_t>(user_data));
  data[1] = htobe_ptr(timer_cb);

  CoreMessage msg;
  BOOST_ASSERT(create_message(NULL, session, true, kTimer, 
                              reinterpret_cast<const char*>(data), 
                              sizeof(uintptr_t) * 2, &msg));
  Services::Instance().Send(handle, msg);
}
}

uint32_t Timers::Add(Service& service,
                     uint32_t trigger_count,
                     const utils::milliseconds& duration,
                     void* user_data,
                     uintptr_t timer_cb) { 
  uint32_t session = service.AllocSession();
  if (duration.count() == 0) {
    Trigger(service.handle(), session, user_data, timer_cb);
    if (trigger_count != 0) {
      if (--trigger_count == 0)
        return session;
    }
  }
  boost::mutex::scoped_lock l(pimpl_->queue_mutex);
  pimpl_->timenode_queue.push(
      new TimeNode(service.handle(), session, user_data, trigger_count, 
                   timer_cb, duration));
  l.unlock();
  return session;
}

void Timers::Tick(void) {
  boost::mutex::scoped_lock l(pimpl_->queue_mutex);
  if (pimpl_->timenode_queue.empty()) return;
  TimeNode* node = pimpl_->timenode_queue.top();
  while (utils::steady_clock::now() >= node->next_time) {
    pimpl_->timenode_queue.pop();
    Trigger(node->handle, node->session, node->user_data, node->timer_cb);
    if (node->trigger_count != 0 && --(node->trigger_count) == 0) {
      delete node; 
      node == 0;
    }
    else {
      node->next_time = utils::steady_clock::now() + node->duration;
      pimpl_->timenode_queue.push(node);
    }
  
    if (node == pimpl_->timenode_queue.top())
      break;
    node = pimpl_->timenode_queue.top();
  }
}

